package dao.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.Socket;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

public class User implements Runnable {
	private int id;
	private String username;
	private String password;
	private DAO dao;
	private Socket connectionSocket;
	private BufferedReader inFromClient;
	private OutputStreamWriter outToClient;
	private boolean isKill;
	private boolean flag;

	public void setDAO(DAO dao) {
		this.dao = dao;
	}

	public User() {
		this.username = "";
		this.password = "";
		isKill = false;
	}

	public User(Socket connetionSocket) {
		this.connectionSocket = connetionSocket;
	}

	public User(String username, String password) {
		this.username = username;
		this.password = password;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void run() {
		String receive;
		StringWriter sent;
		try {

			inFromClient = new BufferedReader(new InputStreamReader(
					connectionSocket.getInputStream(),"UTF-8"));
			outToClient = new OutputStreamWriter(
					connectionSocket.getOutputStream(),"UTF-8");
			while (true) {
				receive = inFromClient.readLine();
				sent = process(receive);
				outToClient.write(sent + "\n");
				outToClient.flush();
				if(isKill) throw new Exception();
			}
		} catch (Exception e) {
			try {
				if(!flag){
					dao.logout(this);
					releaseAllCheckOut();
				}
				connectionSocket.close();
			} catch (IOException e1) {
				//e1.printStackTrace();
			}
		}
	}

	private StringWriter process(String xmlString) {
		try {
			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
			Document doc = docBuilder.parse(new InputSource(new StringReader(xmlString)));
			doc.getDocumentElement().normalize();

			Type type = Type.valueOf(doc.getDocumentElement().getAttribute("type"));
			Action action = Action.valueOf(doc.getDocumentElement().getAttribute("action"));
			StringWriter result = new StringWriter();
			switch (type) {
			case USER: {				
				this.xmlToObject(doc);				
				switch(action){
				case LOGIN:{
					if(checkLogin())
						result = message(docBuilder, "Login success");
					else
						result = message(docBuilder, "Login unsuccess");
					break;
				}
				case LOGOUT:{
					if(logout()){
						result = message(docBuilder, "Logout success");
						isKill = true;
					}
					else
						result = message(docBuilder, "Logout unsucess");
					break;
				}
				case ERROR:{
					flag = true;
					result = message(docBuilder, "success");
					break;
				}
				}
				break;
			}
			case PROJECT: {
				Project project = new Project();
				project.xmlToObject(doc);
				switch(action){
				case ADD:{
					result = message(docBuilder, project.addProject(dao));
					break;
				}
				case UPDATE:{
					result = message(docBuilder, project.updateProject(dao));
					break;
				}
				case DELETE:{
					result = message(docBuilder, project.deleteProject(dao));
					break;
				}
				case GET_ONE:{
					project = dao.getProject(project.getId());
					if(project != null && project.getId() != 0)
					{
						project.getProject(dao);
						result = project.objectToXml(docBuilder);
					}
					else
						result = new StringWriter();
					break;
				}
				case GET_ALL:{
					result = ProjectUtility.getAllProject(dao,docBuilder);
					if(result == null)
						result = message(docBuilder, "Empty project");
					break;
				}
				}
				break;
			}
			case TESTMODULE: {
				TestModule module = new TestModule();
				module.xmlToObject(doc);
				switch(action){
				case ADD:{
					result = message(docBuilder, module.addTestModule(dao));
					break;
				}
				case UPDATE:{
					result = message(docBuilder, module.updateTestModule(dao));
					break;
				}
				case DELETE:{
					result = message(docBuilder, module.deleteTestModule(dao));
					break;
				}
				case RENAME:{
					result = message(docBuilder, module.renameTestModule(dao));
					break;
				}
				}
				break;
			}
			case TESTSUITE: {
				TestSuite suite = new TestSuite();
				suite.xmlToObject(doc);
				switch(action){
				case ADD:{
					result = message(docBuilder, suite.addTestSuite(dao));
					break;
				}
				case UPDATE:{
					result = message(docBuilder, suite.updateTestSuite(dao));
					break;
				}
				case DELETE:{
					result = message(docBuilder, suite.deleteTestSuite(dao));
					break;
				}
				case RENAME:{
					result = message(docBuilder, suite.renameTestSuite(dao));
					break;
				}
				case CHECK_OUT:{
					result = message(docBuilder, suite.checkOutTestSuite(dao));
					break;
				}
				case CHECK_IN:{
					result = message(docBuilder, suite.checkInTestSuite(dao));
					break;
				}
				case RELEASE:{
					result = message(docBuilder, suite.releaseCheckOutTestSuite(dao));
					break;
				}
				case GET_ONE:{
					suite = dao.getTestSuite(suite.getId());					
					if(suite != null && suite.getId() != 0){
						suite.getTestSuite(dao);
						result = suite.objectToXml(docBuilder);
					}
					else
						result = new StringWriter();
					break;
				}
				}
				break;
			}
			case TESTCASE: {
				TestCase testCase = new TestCase();
				testCase.xmlToObject(doc);
				switch(action){
				case ADD:{
					result = message(docBuilder, testCase.addTestCase(dao));
					break;
				}
				case DUPLICATE:{
					result = message(docBuilder, testCase.duplicateTestCase(dao));
					break;
				}
				case UPDATE:{
					result = message(docBuilder, testCase.updateTestCase(dao));
					break;
				}
				case DELETE:{
					result = message(docBuilder, testCase.deleteTestCase(dao));
					break;
				}
				case RENAME:{
					result = message(docBuilder, testCase.renameTestCase(dao));
					break;
				}
				case MOVE:{
					result = message(docBuilder, testCase.moveTestCase(dao));
					break;
				}
				case CHECK_OUT:{
					result = message(docBuilder, testCase.checkOutTestCase(dao));
					break;
				}
				case CHECK_IN:{
					result = message(docBuilder, testCase.checkInTestCase(dao));
					break;
				}
				case RELEASE:{
					result = message(docBuilder, testCase.releaseCheckOutTestCase(dao));
					break;
				}
				case GET_ONE:{
					testCase = dao.getTestCase(testCase.getId());
					if(testCase != null && testCase.getId() != 0)										
						result = testCase.objectToXml(docBuilder);
					else
						result = new StringWriter();
					break;
				}
				}
				break;
			}
			case TESTSCRIPT: {
				TestScript testScript = new TestScript();
				testScript.xmlToObject(doc);
				switch(action){
				case ADD:{
					result = message(docBuilder, testScript.addTestScript(dao));
					break;
				}
				case UPDATE:{
					result = message(docBuilder, testScript.updateTestScript(dao));
					break;
				}
				case DELETE:{
					result = message(docBuilder, testScript.deleteTestScript(dao));
					break;
				}
				case RENAME:{
					result = message(docBuilder, testScript.renameTestScript(dao));
					break;
				}
				case CHECK_OUT:{
					result = message(docBuilder, testScript.checkOutTestScript(dao));
					break;
				}
				case CHECK_IN:{
					result = message(docBuilder, testScript.checkInTestScript(dao));
					break;
				}
				case RELEASE:{
					result = message(docBuilder, testScript.releaseCheckOutTestScript(dao));
					break;
				}
				case GET_ONE:{
					testScript = dao.getTestScript(testScript.getId());
					if(testScript != null && testScript.getId() != 0)						
						result = testScript.objectToXml(docBuilder);
					else
						result = new StringWriter();
					break;
				}
				}
				break;
			}
			case FUNCTION: {
				Function function = new Function();
				function.xmlToObject(doc);
				switch(action){
				case ADD:{
					result = message(docBuilder, function.addFunction(dao));
					break;
				}
				case DUPLICATE:{
					result = message(docBuilder, function.duplicateFunction(dao));
					break;
				}
				case UPDATE:{
					result = message(docBuilder, function.updateFunction(dao));
					break;
				}
				case DELETE:{
					result = message(docBuilder, function.deleteFunction(dao));
					break;
				}
				case RENAME:{
					result = message(docBuilder, function.renameFunction(dao));
					break;
				}
				case CHECK_OUT:{
					result = message(docBuilder, function.checkOutFunction(dao));
					break;
				}
				case CHECK_IN:{
					result = message(docBuilder, function.checkInFunction(dao));
					break;
				}
				case RELEASE:{
					result = message(docBuilder, function.releaseCheckOutFunction(dao));
					break;
				}
				case GET_ONE:{
					function = dao.getFunction(function.getId());
					if(function != null && function.getId() != 0)
						result = function.objectToXml(docBuilder);
					else
						result = new StringWriter();
					break;
				}
				case GET_ONE_NAME:{
					function = dao.getFunction(function.getName(),function.getIdProject());
					if(function != null)
						result = function.objectToXml(docBuilder);
					else
						result = new StringWriter();
					break;
				}
				}
				break;
			}
			case TESTDATA: {
				TestData testData = new TestData();
				testData.xmlToObject(doc);
				switch(action){
				case ADD:{
					result = message(docBuilder, testData.addTestData(dao));
					break;
				}
				case DUPLICATE:{
					result = message(docBuilder, testData.duplicateTestData(dao));
					break;
				}
				case UPDATE:{
					result = message(docBuilder, testData.updateTestData(dao));
					break;
				}
				case DELETE:{
					result = message(docBuilder, testData.deleteTestData(dao));
					break;
				}
				case RENAME:{
					result = message(docBuilder, testData.renameTestData(dao));
					break;
				}
				case CHECK_OUT:{
					result = message(docBuilder, testData.checkOutTestData(dao));
					break;
				}
				case CHECK_IN:{
					result = message(docBuilder, testData.checkInTestData(dao));
					break;
				}
				case RELEASE:{
					result = message(docBuilder, testData.releaseCheckOutTestData(dao));
					break;
				}
				case GET_ONE:{
					testData = dao.getTestData(testData.getId());
					if(testData != null && testData.getId() != 0)						
						result = testData.objectToXml(docBuilder);
					else
						result = new StringWriter();
					break;
				}
				}
				break;
			}
			case TESTRESULT: {
				TestResult testResult = new TestResult();
				testResult.xmlToObject(doc);
				switch(action){
				case ADD:{
					result = message(docBuilder, testResult.addTestResult(dao));
					break;
				}
				case UPDATE:{
					result = message(docBuilder, testResult.updateTestResult(dao));
					break;
				}
				case DELETE:{
					result = message(docBuilder, testResult.deleteTestResult(dao));
					break;
				}
				case GET_ONE:{
					testResult = dao.getTestResult(testResult.getId());
					if(testResult != null && testResult.getId() != 0)						
						result = testResult.objectToXml(docBuilder);
					else
						result = new StringWriter();
					break;
				}
				}
				break;
			}
			}
			return result;
		} catch (Exception e) {
			//e.printStackTrace();
			return null;
		}
	}

	private boolean checkLogin() {
		try {
			if (dao.checkLogin(this)) {
				dao.login(this);
				return true;
			} else {
				return false;
			}
		} catch (Exception e) {
			return false;
		}
	}
	
	private boolean logout(){
		try{
			dao.logout(this);
			return true;
		}catch(Exception e){
			return false;
		}
	}
	
	private void releaseAllCheckOut(){
		dao.releaseAllCheckOutTestCase(this.getUsername());
		dao.releaseAllCheckOutTestScript(this.getUsername());
		dao.releaseAllCheckOutFunction(this.getUsername());
		dao.releaseAllCheckOutTestData(this.getUsername());
		dao.releaseAllCheckOutTestSuite(this.getUsername());		
	}

	public StringWriter objectToXml(DocumentBuilder docBuilder) {
		try {
			// document elements
			Document doc = docBuilder.newDocument();
			Element document = doc.createElement("document");
			doc.appendChild(document);

			// set attribute to document element
			Attr type = doc.createAttribute("type");
			type.setValue("USER");
			document.setAttributeNode(type);

			Attr action = doc.createAttribute("action");
			action.setValue("");
			document.setAttributeNode(action);

			// head elements
			Element head = doc.createElement("head");
			document.appendChild(head);

			// id elements
			Element id = doc.createElement("id");
			id.appendChild(doc.createTextNode(String.valueOf(this.getId())));
			head.appendChild(id);

			// username elements
			Element username = doc.createElement("username");
			username.appendChild(doc.createTextNode(this.getUsername()));
			head.appendChild(username);

			// password elements
			Element password = doc.createElement("password");
			password.appendChild(doc.createTextNode(this.getPassword()));
			head.appendChild(password);

			// write the content into xml file
			TransformerFactory transformerFactory = TransformerFactory
					.newInstance();
			Transformer transformer = transformerFactory.newTransformer();
			DOMSource source = new DOMSource(doc);
			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			transformer.transform(source, result);
			return sw;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public void xmlToObject(Document doc) {
		try {
			NodeList nodeList = doc.getElementsByTagName("head");
			Element element = (Element) nodeList.item(0);
			this.setId(Integer.parseInt(element.getElementsByTagName("id")
					.item(0).getTextContent()));
			this.setUsername(element.getElementsByTagName("username").item(0)
					.getTextContent());
			this.setPassword(element.getElementsByTagName("password").item(0)
					.getTextContent());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	private StringWriter message(DocumentBuilder docBuilder,String info){
		try {
			// message elements
			Document doc = docBuilder.newDocument();
			Element message = doc.createElement("message");
			message.appendChild(doc.createTextNode(info));
			doc.appendChild(message);

			// write the content into xml file
			TransformerFactory transformerFactory = TransformerFactory
					.newInstance();
			Transformer transformer = transformerFactory.newTransformer();
			DOMSource source = new DOMSource(doc);
			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			transformer.transform(source, result);
			return sw;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
}
